#include "stdafx.h"
#include "GraphicsManager.h"
#include "VertexFormats.h"
#include "ShaderContainer.h"
#include "VertexBuffer.h"
#include "ConstantBuffer.h"
#include "IDrawable.h"
#include "Fonts.h"
#include <assert.h>
#include "RenderTarget.h"
#include "RenderTargetManager.h"
#include "SamplerStateManager.h"
#include "DepthBuffer.h"

GraphicsManager* GraphicsManager::instance = nullptr;

GraphicsManager::GraphicsManager(HWND hwnd, int backBufferWidth, int backBufferHeight)
{
	ZeroMemory(this, sizeof(GraphicsManager));
	use_shaders = true;
#pragma region make swap chain
	DXGI_SWAP_CHAIN_DESC sc_desc;

	ZeroMemory(&sc_desc, sizeof(DXGI_SWAP_CHAIN_DESC));

	// fill in members for the swap chains description
	sc_desc.BufferCount = 1;
	sc_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	sc_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sc_desc.OutputWindow = hwnd;
	sc_desc.SampleDesc.Count = 1;
	sc_desc.Windowed = true;
	UINT flags;
#if _DEBUG
	flags = D3D11_CREATE_DEVICE_DEBUG;
#else
	flags = 0;
#endif

	HRESULT hr = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, NULL, NULL, D3D11_SDK_VERSION, &sc_desc, &graphics.swap_chain, &graphics.device, NULL, &graphics.device_context);
	assert(SUCCEEDED(hr) && "Failed to inilitize DirectX!");
	SamplerStateManager::device = graphics.device;
	graphics.device->GetImmediateContext(&graphics.device_context);


	ID3D11Texture2D* backBuffer_tex;
	graphics.swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backBuffer_tex);
	render_tgt = new RenderTarget(backBuffer_tex, graphics.device, false);
	RenderTargetManager::getInstance()->SetRenderTarget(render_tgt, 0);

	depth_buffer = new DepthBuffer(graphics.device, render_tgt->getWidth(), render_tgt->getHeight());
	RenderTargetManager::getInstance()->setDepthBuffer(depth_buffer);

	DXGI_SWAP_CHAIN_DESC viewport_swapChain_desc;
	graphics.viewport.TopLeftX = 0;
	graphics.viewport.TopLeftY = 0;
	graphics.swap_chain->GetDesc(&viewport_swapChain_desc);
	graphics.viewport.Width = float(viewport_swapChain_desc.BufferDesc.Width);
	graphics.viewport.Height = float(viewport_swapChain_desc.BufferDesc.Height);
	graphics.viewport.MinDepth = 0;
	graphics.viewport.MaxDepth = 1;

	SamplerStateManager::context = graphics.device_context;
	SamplerStateManager::device = graphics.device;
	SamplerStateManager::GetInstance()->setCurrentSamplerState(SamplerStateManager::GetInstance()->samplers.linearWrap, { SHADER_INDEX_PIXEL_SHADER, 0 });
}

void GraphicsManager::setSamplerState(ID3D11SamplerState* value)
{
	graphics.device_context->PSSetSamplers(0, 1, &value);
}

void GraphicsManager::setBlendState(ID3D11BlendState* value)
{
	static float factor[4];
	graphics.device_context->OMSetBlendState(value, factor, INT_MAX);
}

void GraphicsManager::setStencilState(ID3D11DepthStencilState* value)
{
	graphics.device_context->OMSetDepthStencilState(value, 0);
}

void GraphicsManager::setRasterizerState(ID3D11RasterizerState* value)
{
	graphics.device_context->RSSetState(value);
}

ID3D11SamplerState* GraphicsManager::getSamplerState(void) const
{
	ID3D11SamplerState* outval;
	graphics.device_context->PSGetSamplers(0, 1, &outval);

	return outval;
}

ID3D11PixelShader* GraphicsManager::getPixelShader(void) const
{
	ID3D11PixelShader* outval;
	graphics.device_context->PSGetShader(&outval, nullptr, nullptr);
	return outval;
}

ID3D11VertexShader* GraphicsManager::getVertexShader(void) const
{
	ID3D11VertexShader* outval;
	graphics.device_context->VSGetShader(&outval, nullptr, nullptr);

	return outval;
}
ID3D11DepthStencilState* GraphicsManager::getDepthStencilState(void) const
{
	ID3D11DepthStencilState* outval;
	graphics.device_context->OMGetDepthStencilState(&outval, nullptr);

	return outval;
}

ID3D11RasterizerState* GraphicsManager::getRasterizerState(void) const
{
	ID3D11RasterizerState* outval;
	graphics.device_context->RSGetState(&outval);

	return outval;
}

ID3D11VertexShader*	GraphicsManager::createVertexShader(void* pData, unsigned int bufferSize)
{
	ID3D11VertexShader* outval;
	HRESULT hr = graphics.device->CreateVertexShader(pData, bufferSize, nullptr, &outval);

	return outval;
}

ID3D11GeometryShader* GraphicsManager::createGeometryShader(void* pData, unsigned int bufferSize)
{
	ID3D11GeometryShader* outval;
	graphics.device->CreateGeometryShader(pData, bufferSize, nullptr, &outval);

	return outval;
}

ID3D11PixelShader* GraphicsManager::createPixelShader(void* pData, unsigned int bufferSize)
{
	ID3D11PixelShader* outval;
	graphics.device->CreatePixelShader(pData, bufferSize, nullptr, &outval);

	return outval;
}

/*static*/ void GraphicsManager::Inilitizie(HWND hwnd, int backBufferWidth, int backBufferHeight)
{
	assert(instance == nullptr && "There can be only ONE!! instance of graphics manager...");
	instance = new GraphicsManager(hwnd, backBufferWidth, backBufferHeight);
}

/*static*/ void GraphicsManager::ShutDown(void)
{
	delete instance;
	instance = nullptr;
}

GraphicsManager::~GraphicsManager(void)
{
	graphics.Release();
	delete depth_buffer;
	delete render_tgt;
	if (graphics.device_context)
		graphics.device_context->Release();
}

void GraphicsManager::Begin(void)
{
	// define sissor rect
	D3D11_RECT scissor_rect;
	scissor_rect.left = 0;
	scissor_rect.right = long(graphics.viewport.Width);
	scissor_rect.top = 0;
	scissor_rect.bottom = long(graphics.viewport.Height);

	graphics.device_context->RSSetScissorRects(1, &scissor_rect);

	graphics.device_context->RSSetViewports(1, &graphics.viewport);
	
	began = true;
}

void GraphicsManager::SendBufferToDevice(ID3D11Buffer* buffer, PiplineIndex slot)
{
	if (slot.shader_index == 0)
		graphics.device_context->VSSetConstantBuffers(slot.slot, 1, &buffer);
	if (slot.shader_index == 1)
		graphics.device_context->PSSetConstantBuffers(slot.slot, 1, &buffer);
	if (slot.shader_index == 2)
		graphics.device_context->GSSetConstantBuffers(slot.slot, 1, &buffer);
}

void GraphicsManager::End(void)
{
	assert(began && "Begin must be called before end!");

	graphics.swap_chain->Present(0, 0);

	began = false;
}

ID3D11InputLayout* GraphicsManager::createInputLayout(D3D11_INPUT_ELEMENT_DESC* layout, const BYTE* shaderCode, unsigned int shaderCodeSize, unsigned int numElements)
{
	ID3D11InputLayout* outval;
	HRESULT hr = graphics.device->CreateInputLayout(layout, numElements, shaderCode, shaderCodeSize, &outval);

	return outval;
}

void GraphicsManager::MakeBuffer(ID3D11Buffer** buffer, D3D11_BUFFER_DESC* buffer_desc, void* subresource_data)
{
	D3D11_SUBRESOURCE_DATA subresource;
	ZeroMemory(&subresource, sizeof(D3D11_SUBRESOURCE_DATA));
	if (subresource_data != nullptr)
		subresource.pSysMem = subresource_data;

	HRESULT hr = graphics.device->CreateBuffer(buffer_desc, subresource_data ? &subresource : nullptr, buffer);
}

/*static*/ GraphicsManager* GraphicsManager::GetInstance(void)
{
	return instance;
}

void GraphicsManager::Draw(IDrawable* shape)
{	
	// send current vertex buffer to device
	unsigned int stride = shape->getVertexBuffer()->getVertexStride();
	unsigned int offset = 0;

	ID3D11Buffer* vertex_buffer = shape->getVertexBuffer()->getVertexBuffer();
	ID3D11Buffer* index_buffer = shape->getVertexBuffer()->getIndexBuffer();
		
	if (use_shaders)
		shape->getShaderContainer()->Bind();
	
	// set topology
	graphics.device_context->IASetPrimitiveTopology(shape->getVertexBuffer()->getTopology());
	// set input layout
	graphics.device_context->IASetInputLayout(shape->getVertexBuffer()->getInputLayout());
	
	// set texture
	IGraphicsResource** srvs = shape->getResourceViews();
	for (int i = 0; i < shape->getNumResourceViews(); i++)
	{
		if (!srvs[i])
		{
			ID3D11ShaderResourceView* null_srv = nullptr;
			graphics.device_context->PSSetShaderResources(i, 1, &null_srv);
			continue;
		}
		ID3D11ShaderResourceView* srv = srvs[i]->getSrv();
		graphics.device_context->PSSetShaderResources(i, 1, &srv);
	}

	
	// set vertex buffer
	graphics.device_context->IASetVertexBuffers(0, 1, &vertex_buffer, &stride, &offset);
	// set index buffer
	graphics.device_context->IASetIndexBuffer(index_buffer, DXGI_FORMAT_R32_UINT, 0);
	// render geometry
	graphics.device_context->DrawIndexed(shape->getVertexBuffer()->getIndexCount(), 0, 0);

}

void GraphicsManager::ClearDepthBuffer(float clearValue)
{
	assert(graphics.device_context != nullptr && "An active context must be set in order to clear depth buffer");

	RenderTargetManager* pRtm = RenderTargetManager::getInstance();
	graphics.device_context->ClearDepthStencilView(depth_buffer->getStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, clearValue, 1);
}

void GraphicsManager::AllocateContextSlot(unsigned int index)
{
}

ID3D11BlendState* GraphicsManager::createBlendState(D3D11_BLEND_DESC* desc)
{
	ID3D11BlendState* outval;
	HRESULT hr = graphics.device->CreateBlendState(desc, &outval);

	return outval;
}

void GraphicsManager::ClearBackBuffer(Vector4& clearColor)
{
	assert(graphics.device_context && "An active context must be set in order to clear back buffer");
	float color[4];
	memcpy(color, &clearColor, sizeof(float) * 4);
	RenderTargetManager* pRtm = RenderTargetManager::getInstance();
	for (int i = 0; i < D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT; i++)
	{
		if (pRtm->getRenderTarget(i))
			graphics.device_context->ClearRenderTargetView(pRtm->getRenderTarget(i)->getDXRenderTarget(), color);
	}
}

ID3D11RenderTargetView* GraphicsManager::createRenderTarget(D3D11_TEXTURE2D_DESC* desc, ID3D11Texture2D** texture_out, D3D11_SUBRESOURCE_DATA* subresource_data)
{
	graphics.device->CreateTexture2D(desc, subresource_data, texture_out);

	ID3D11RenderTargetView* outval;
	graphics.device->CreateRenderTargetView(*texture_out, nullptr, &outval);

	return outval;
}

ID3D11ShaderResourceView* GraphicsManager::createSRV(D3D11_SHADER_RESOURCE_VIEW_DESC* desc, ID3D11Resource* resource)
{
	ID3D11ShaderResourceView* outval;
	graphics.device->CreateShaderResourceView(resource, desc, &outval);

	return outval;
}

ID3D11DepthStencilView* GraphicsManager::createDepthStencilView(D3D11_TEXTURE2D_DESC* desc, ID3D11Texture2D** texture_out, D3D11_SUBRESOURCE_DATA* subresource_data)
{
	graphics.device->CreateTexture2D(desc, subresource_data, texture_out);

	ID3D11DepthStencilView* outval;
	graphics.device->CreateDepthStencilView(*texture_out, nullptr, &outval);

	return outval;
}

ID3D11DepthStencilState* GraphicsManager::createStencilState(D3D11_DEPTH_STENCIL_DESC* desc)
{
	ID3D11DepthStencilState* outval;
	graphics.device->CreateDepthStencilState(desc, &outval);

	return outval;
}

ID3D11SamplerState* GraphicsManager::createSamplerState(D3D11_SAMPLER_DESC* desc)
{
	ID3D11SamplerState* outval;
	graphics.device->CreateSamplerState(desc, &outval);

	return outval;
}